#include <fermat/service/service.h>
#include <fermat/common/file.h>
#include <fermat/common/string.h>
#include <fermat/common/logging.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <cstdlib>
#include <time.h>
#include <fcntl.h>

using std::string;
using fermat::parser::Properties;
using fermat::event::EventLoop;
using fermat::Timestamp;
using fermat::File;
using fermat::event::Channel;
using std::cout;
using std::endl;

namespace fermat {

namespace service {

namespace detail{

int createSignalfd(int fd, sigset_t *mask)
{

	if(sigprocmask(SIG_BLOCK, mask, NULL) == -1) {
		LOG_ERROR("sigprocmask error errno: ", errno);
		return -1;
	}
	
	int refd = signalfd(fd, mask ,SFD_NONBLOCK | SFD_CLOEXEC);
	
	return refd;
	
}

}


Service::Service()
{
	_reconf_count = 0;
	sigemptyset(&_sigmask);
	sigaddset(&_sigmask, SIGQUIT);
	sigaddset(&_sigmask, SIGTERM);
		
}

Service:: ~Service()
{
	// just let system recycle it
	/*
	if(_signalfd) {
		_sigChannel->disableAll();
  		_sigChannel->remove();
  		::close(_signalfd);
		_signalfd = -1;
	}
	*/
}

void Service::addSignal(int sig)
{
	sigaddset(&_sigmask, sig);
}

void Service::stop()
{
	_active = false;
	_sigChannel->disableAll();
  	_sigChannel->remove();
  	::close(_signalfd);
	_signalfd = -1;
	File f(_pidFile);
	f.remove();
	_loop->quit();
}
void Service::setParseOption()
{

}

int Service::main(int argc, char* argv[])
{
	
	_isdaemon = false;

	_args.add("version", 'v', "show version");
	_args.add<string>("config", 'c', "config file path", false,"./service.conf");
	_args.add<string>("command", 'k', "user cmd [start|stop|reload]", false);
	_args.add("tconf", 't', "test conf");
	_args.parse_check(argc,argv);

	if(_args.exist("version")) {
		version();
		exit(0);
	}

	 _confFile =  _args.get<string>("config");
	 std::shared_ptr<Properties> tmpconf = loadConf(_confFile);
	 if(!testConf(tmpconf)) {
		 cout<<"Config file load error to check config file"<<endl;
		 exit(0);
	 }

	if(_args.exist("tconf")) {
		cout<<"Config file load ok"<<endl;
		exit(0);
	}

	setConf(tmpconf);

	string cmd  =  _args.get<string>("command");
	if(cmd.empty()) {
		cmd = "start";
	}
	if(cmd == "start") {
		start();	
		
	} else if(cmd == "stop") {
		configStop();
	} else if(cmd == "reload") {
		configReload();
	}
	return 0;
	
}
void Service::configStop()
{
		File f(_pidFile);

		string pidcontent;
		if(!f.exists()) {
			cout<<"[INFO]: can not found pidfile["<<_pidFile<<"]..."<<endl;
			cout<<"[INFO]: program not running"<<endl;
			exit(0);
		}
		if(!f.readToString(pidcontent)) {
			 cout<<"[ERROR]: can not read pidfile["<<_pidFile<<"]..."<<endl;
		}
		
		int64_t opid = fermat::string_to_int(pidcontent);
		if(opid > 0) {
			if(kill(opid, SIGQUIT) != 0) {
				 cout<<"[ERROR]: kill pid["<<opid<<"] failed..."<<endl;
				 f.remove();
			}
		} else {
			 cout<<"[ERROR]:pidfile content error["<<pidcontent<<"]...."<<endl;
			  f.remove();
		}
		exit(0);	
	
}

void Service::configReload()
{
		File f(_pidFile);

		string pidcontent;
		if(!f.exists()) {
			cout<<"[INFO]: can not found pidfile["<<_pidFile<<"]..."<<endl;
			cout<<"[INFO]: program not running"<<endl;
			exit(0);
		}
		if(!f.readToString(pidcontent)) {
			 cout<<"[ERROR]: can not read pidfile["<<_pidFile<<"]..."<<endl;
		}
		
		int64_t opid = fermat::string_to_int(pidcontent);
		if(opid > 0) {
			if(kill(opid, SIGTERM) != 0) {
				 cout<<"[ERROR]: signal pid["<<opid<<"] reload failed..."<<endl;
			}
		} else {
			 cout<<"[ERROR]:pidfile content error["<<pidcontent<<"]...."<<endl;
		}
		exit(0);
}

void Service::reload()
{
	_reconf_count++;
	std::shared_ptr<Properties> tmpconf = loadConf(_confFile);
	if(!tmpconf) {
		LOG_ERROR("reload error, can not load conf");
		return;
	}
	if(!testConf(tmpconf)) {
		LOG_ERROR("reload error, test conf fail");
		return;
	}
	setConf(tmpconf);
	resetLogger();
	appReConf(_conf);	
}

std::shared_ptr<Properties> Service::loadConf(std::string& confpath)
{
	File cf(confpath);
	if(!cf.exists()) {
		std::cout<<"[ERROR]: config file not exist： "<<confpath<<std::endl;
		return std::shared_ptr<fermat::parser::Properties>();
	}
	std::shared_ptr<fermat::parser::Properties> ret(new fermat::parser::Properties());
	if(!ret) {
		std::cout<<"[ERROR]: alloc config error"<<std::endl;
		return ret;
	}

	if(ret->loadProperties(confpath.c_str(), '=', false)) {
		std::cout<<"[ERROR]: config parse error"<<std::endl;
		 return std::shared_ptr<fermat::parser::Properties>();
	}
	return ret;
}
void Service::resetLogger()
{
	fermat::log::LoggerPtr old =  fermat::getFmtLogger();
	std::string lname = "fmt-sys" + fermat::to_str(_reconf_count);
	File f(_logbase);
	if(!f.exists()) {
		f.createDirectories();
	}

	fermat::log::LoggerPtr cur =  fermat::log::create<fermat::log::daily_file_sink_mt>(lname, _logName, 
                                        _logExtent, 
                                        0, 
                                        0, 
                                        false);
	fermat::setFmtLogger(cur);
}

bool Service::testConf(std::shared_ptr<Properties> &conf)
{
	if(!conf) {
		cout<<"[ERROR]: config is nullptr"<<endl;
		return false;
	}
	std::string value;
	value = conf->getValue("pidfile", "");
	if(value.empty()) {
		cout<<"[ERROR]: Not found pidfile"<<endl;
		return false;
	}

	value = conf->getValue("service_work_dir", "");
	if(value.empty()) {
		cout<<"[ERROR]: Not found service_work_dir"<<endl;
		return false;
	}

	value = conf->getValue("service_log_dir", "");
	if(value.empty()) {
		cout<<"[ERROR]: Not found service_log_dir"<<endl;
		return false;
	}

	if(value[0] != '/') {
		cout<<"[ERROR]:service_log_dir must be absolute path"<<endl;
		return false;
	}

	value = conf->getValue("service_log_name_prefix", "");
	if(value.empty()) {
		cout<<"[ERROR]: Not found service_log_name_prefix"<<endl;
		return false;
	}
	
	if(value[0] == '/') {
		cout<<"[ERROR]:service_log_name_prefix can not be absolute path"<<endl;
		return false;
	}

	value = conf->getValue("service_log_name_extension", "");
	if(value.empty()) {
		cout<<"[ERROR]: Not found service_log_name_extension"<<endl;
		return false;
	}

	std::string level = conf->getValue("service_log_level", "");
	if(value.empty()) {
		cout<<"[ERROR]: Not found service_log_level"<<endl;
		return false;
	}
	bool find = false;
	if(level == "trace" || level == "TRACE") {
		find = true;
	} else if(level == "debug" || level == "DEBUG") {
		find = true;
	}else if(level == "info" || level == "INFO") {
		find = true;
	}else if(level == "WARN" || level == "warn") {
		find = true;
	}else if(level == "error" || level == "ERROR") {
		find = true;
	}else if(level == "critical" || level == "CRITICAL") {
		find = true;
	} 

	if(!find) {
		cout<<"[ERROR]: service_log_level error string"<<endl;
		return false;	
	}
	return appTestConf(conf);

}
void Service::setConf(std::shared_ptr<Properties> &conf)
{
	_conf = conf;
	std::string daemon = conf->getValue("daemon_mode", "true");

	if(daemon == "true") {
		_isdaemon = true;
	} else {
		_isdaemon = false;	
	}
	_pidFile = conf->getValue("pidfile", "");
	
	_workdir = conf->getValue("service_work_dir", "");

	_logbase = conf->getValue("service_log_dir", "");

	_logPrefix = conf->getValue("service_log_name_prefix", "");

	_logExtent = conf->getValue("service_log_name_extension", "");

	std::string level = conf->getValue("service_log_level", "");

	_logName = _logbase;
	if(_logbase[_logbase.size()-1] != '/' ) {
		_logName +="/";
		_logName += _logPrefix;
	} else {
		_logName += _logPrefix;
	}
	if(level == "trace" || level == "TRACE") {
		_loglevel = fermat::log::ll_trace;
	} else if(level == "debug" || level == "DEBUG") {
		_loglevel = fermat::log::ll_debug;
	}else if(level == "info" || level == "INFO") {
		_loglevel = fermat::log::ll_info;
	}else if(level == "WARN" || level == "warn") {
		_loglevel = fermat::log::ll_warn;
	}else if(level == "error" || level == "ERROR") {
		_loglevel = fermat::log::ll_err;
	}else if(level == "critical" || level == "CRITICAL") {
		_loglevel = fermat::log::ll_critical;
	} 
}
bool Service::appReConf(std::shared_ptr<Properties> &)
{
	return true;
}

bool Service::appTestConf(std::shared_ptr<Properties> &)
{
	return true;
}
void Service::runDaemon()
{
	int pid;
	if((pid=fork()) > 0 ) {
		exit(0);
	} else if(pid < 0) {
		exit(1);
	}
	
	setsid();
	if((pid=fork()) > 0) {
		exit(0);
	} else if(pid< 0) {		
		exit(1);
	}

	for(int i=0;i< NOFILE;++i) {
		close(i);
	}
	
	int fd = open("/dev/null",O_RDWR| O_CLOEXEC );
	assert( fd == 0 );
	fd = dup2(0,1);
	assert( fd == 1 );
	fd = dup2(1,2);
	assert( fd == 2 ); 
	(void)fd;
	if(!_workdir.empty()) {
		int ret = chdir(_workdir.c_str());
		(void)ret;
		
	}
	umask(0);
	return;	
}

void Service::start() 
{
	fermat::File f(_pidFile);
	if(f.exists()) {
		std::cout<<"[ERROR] program is running"<<std::endl;
		exit(0);
	}

	if(_isdaemon ) {
		runDaemon();
	}
	resetLogger();

	EventLoop loop;
	_loop = &loop;
	

	_signalfd = detail::createSignalfd(-1, &_sigmask);
	if(_signalfd < 3) {
		LOG_ERROR("create signal fd error: {}", errno);
		exit(0);
	}
	
	_sigChannel.reset(new Channel(_loop,_signalfd));
	_sigChannel->setReadCallback(std::bind(&Service::onReadSignal,this,std::placeholders::_1));
	_sigChannel->enableReading();
	//write pid file
	std::string pidnum = fermat::to_str(::getpid());
	f.writeToFile(pidnum);
	
	_active = true;
	int ret = run();
	if(ret == 0 ) {
		_loop->loop();
	}
	dispose();
}

std::string Service::version()
{
	return string("fermat energy 1.0");
}

void Service::onReadSignal(Timestamp t)
{
	(void)t;
	struct signalfd_siginfo fdsi;
	memset(&fdsi,0,sizeof(fdsi));
	ssize_t s = read(_signalfd, &fdsi, sizeof(struct signalfd_siginfo));
	if (s != sizeof(struct signalfd_siginfo)) {
		LOG_ERROR("read signal error size: {} should be : {}", s, sizeof(struct signalfd_siginfo));
		return stop();
	}
	
	LOG_INFO("recv signal: {}", fdsi.ssi_signo);
	if(!userSignalOpt(fdsi.ssi_signo)) {
		defaultSignalOpt(fdsi.ssi_signo);
	}
}

bool Service::userSignalOpt(int sig)
{
	(void)sig;
	return false;
}
void Service::defaultSignalOpt(int sig)
{
	if (sig == SIGQUIT) {
		LOG_INFO("SIGQUIT signal to stop");
		stop();
	}
	
	if (sig == SIGTERM) {
		LOG_INFO("kill signal to reload");
			reload();
	}
}
}
}

